﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LibNoise.Utilities {
	/// Implements a noise map, a 2-dimensional array of floating-point
    /// values.
	public class NoiseMap {
		#region Private Members
		/// Value used for all positions outside of the noise map.
        private float m_borderValue;

        /// The current height of the noise map.
        private int m_height;

        /// The amount of memory allocated for this noise map.
        ///
        /// This value is equal to the number of @a float values allocated for
        /// the noise map, not the number of bytes.
        private int m_memUsed;

        /// A pointer to the noise map buffer.
        private float[] m_pNoiseMap;

        /// The stride amount of the noise map.
        private int m_stride;

        /// The current width of the noise map.
        private int m_width;
		#endregion

        /// Constructor.
        ///
        /// Creates an empty noise map.
        public NoiseMap (){
		  InitObj ();
		}

        /// Constructor.
        ///
        /// @param width The width of the new noise map.
        /// @param height The height of the new noise map.
        ///
        /// @pre The width and height values are positive.
        /// @pre The width and height values do not exceed the maximum
        /// possible width and height for the noise map.
        ///
        /// @throw noise::ExceptionInvalidParam See the preconditions.
        /// @throw noise::ExceptionOutOfMemory Out of memory.
        ///
        /// Creates a noise map with uninitialized values.
        ///
        /// It is considered an error if the specified dimensions are not
        /// positive.
        public NoiseMap (int width, int height){
			InitObj();
			SetSize(width, height);
		}

        /// Copy constructor.
        ///
        /// @throw noise::ExceptionOutOfMemory Out of memory.
        public NoiseMap (NoiseMap rhs){
			InitObj();
			CopyNoiseMap(rhs);
		}

		/// Initializes the noise map object.
        ///
        /// @pre Must be called during object construction.
        /// @pre The noise map buffer must not exist.
        private void InitObj (){
			m_pNoiseMap = null;
			m_height    = 0;
			m_width     = 0;
			m_stride    = 0;
			m_memUsed   = 0;
			m_borderValue = 0.0f;
		}

        /// Clears the noise map to a specified value.
        ///
        /// @param value The value that all positions within the noise map are
        /// cleared to.
		//public void Clear(float value){
		//    if (m_pNoiseMap != null) {
		//        for (int y = 0; y < m_height; y++) {
		//            float* pDest = GetSlabPtr (0, y);
		//            for (int x = 0; x < m_width; x++) {
		//                *pDest++ = value;
		//            }
		//        }
		//    }
		//}

        /// Returns the value used for all positions outside of the noise map.
        ///
        /// @returns The value used for all positions outside of the noise
        /// map.
        ///
        /// All positions outside of the noise map are assumed to have a
        /// common value known as the <i>border value</i>.
        public float GetBorderValue()
        {
			return m_borderValue;
        }

        /// Returns a const pointer to a slab.
        ///
        /// @returns A const pointer to a slab at the position (0, 0), or
        /// @a NULL if the noise map is empty.
        public float[] GetConstSlabPtr()
        {
			return m_pNoiseMap;
        }

        /// Returns a const pointer to a slab at the specified row.
        ///
        /// @param row The row, or @a y coordinate.
        ///
        /// @returns A const pointer to a slab at the position ( 0, @a row ),
        /// or @a NULL if the noise map is empty.
        ///
        /// @pre The coordinates must exist within the bounds of the noise
        /// map.
        ///
        /// This method does not perform bounds checking so be careful when
        /// calling it.
		//public const float* GetConstSlabPtr (int row) const
		//{
		//	return GetConstSlabPtr (0, row);
		//}

        /// Returns a const pointer to a slab at the specified position.
        ///
        /// @param x The x coordinate of the position.
        /// @param y The y coordinate of the position.
        ///
        /// @returns A const pointer to a slab at the position ( @a x, @a y ),
        /// or @a NULL if the noise map is empty.
        ///
        /// @pre The coordinates must exist within the bounds of the noise
        /// map.
        ///
        /// This method does not perform bounds checking so be careful when
        /// calling it.
		public float GetConstSlabPtr(int x, int y)
		{
		    return m_pNoiseMap[x + (m_stride * y)];
		}

        /// Returns the height of the noise map.
        ///
        /// @returns The height of the noise map.
        public int GetHeight()
        {
			return m_height;
        }

        /// Returns the amount of memory allocated for this noise map.
        ///
        /// @returns The amount of memory allocated for this noise map.
        ///
        /// This method returns the number of @a float values allocated.
        public int GetMemUsed()
        {
			return m_memUsed;
        }

        /// Returns a pointer to a slab.
        ///
        /// @returns A pointer to a slab at the position (0, 0), or @a NULL if
        /// the noise map is empty.
        public float[] GetSlabPtr()
        {
			return m_pNoiseMap;
        }

        /// Returns a pointer to a slab at the specified row.
        ///
        /// @param row The row, or @a y coordinate.
        ///
        /// @returns A pointer to a slab at the position ( 0, @a row ), or
        /// @a NULL if the noise map is empty.
        ///
        /// @pre The coordinates must exist within the bounds of the noise
        /// map.
        ///
        /// This method does not perform bounds checking so be careful when
        /// calling it.		//public float[] GetSlabPtr(int row)
		//{
		//    return GetSlabPtr(0, row);
		//}


        /// Returns a pointer to a slab at the specified position.
        ///
        /// @param x The x coordinate of the position.
        /// @param y The y coordinate of the position.
        ///
        /// @returns A pointer to a slab at the position ( @a x, @a y ) or
        /// @a NULL if the noise map is empty.
        ///
        /// @pre The coordinates must exist within the bounds of the noise
        /// map.
        ///
        /// This method does not perform bounds checking so be careful when
        /// calling it.
		public float GetSlabPtr(int x, int y)
		{
			return m_pNoiseMap[(int)x + ((int)m_stride * (int)y)];
		}


		internal void SetSlabPtr(int x, int y, float val) {
			m_pNoiseMap[(int)x + ((int)m_stride * (int)y)] = val;
		}

        /// Returns the stride amount of the noise map.
        ///
        /// @returns The stride amount of the noise map.
        ///
        /// - The <i>stride amount</i> is the offset between the starting
        ///   points of any two adjacent slabs in a noise map.
        /// - The stride amount is measured by the number of @a float values
        ///   between these two points, not by the number of bytes.
        public int GetStride ()
        {
			return m_stride;
        }

        /// Returns a value from the specified position in the noise map.
        ///
        /// @param x The x coordinate of the position.
        /// @param y The y coordinate of the position.
        ///
        /// @returns The value at that position.
        ///
        /// This method returns the border value if the coordinates exist
        /// outside of the noise map.
		//public float GetValue(int x, int y){
		//    if (m_pNoiseMap != null) {
		//        if (x >= 0 && x < m_width && y >= 0 && y < m_height) {
		//            return *(GetConstSlabPtr (x, y));
		//        }
		//    }
		//    // The coordinates specified are outside the noise map.  Return the border
		//    // value.
		//    return m_borderValue;
		//}

        /// Returns the width of the noise map.
        ///
        /// @returns The width of the noise map.
        public int GetWidth()
        {
			return m_width;
        }

        /// Reallocates the noise map to recover wasted memory.
        ///
        /// @throw noise::ExceptionOutOfMemory Out of memory.  (Yes, this
        /// method can return an out-of-memory exception because two noise
        /// maps will temporarily exist in memory during this call.)
        ///
        /// The contents of the noise map is unaffected.
		//public void ReclaimMem (){
		//    size_t newMemUsage = CalcMinMemUsage (m_width, m_height);
		//    if (m_memUsed > newMemUsage) {
		//        // There is wasted memory.  Create the smallest buffer that can fit the
		//        // data and copy the data to it.
		//        float* pNewNoiseMap = NULL;
		//        try {
		//            pNewNoiseMap = new float[newMemUsage];
		//        }
		//        catch (...) {
		//            throw noise::ExceptionOutOfMemory ();
		//        }
		//        memcpy (pNewNoiseMap, m_pNoiseMap, newMemUsage * sizeof (float));
		//        delete[] m_pNoiseMap;
		//        m_pNoiseMap = pNewNoiseMap;
		//        m_memUsed = newMemUsage;
		//    }
		//}

        /// Sets the value to use for all positions outside of the noise map.
        ///
        /// @param borderValue The value to use for all positions outside of
        /// the noise map.
        ///
        /// All positions outside of the noise map are assumed to have a
        /// common value known as the <i>border value</i>.
        public void SetBorderValue (float borderValue)
        {
			m_borderValue = borderValue;
        }

        /// Sets the new size for the noise map.
        ///
        /// @param width The new width for the noise map.
        /// @param height The new height for the noise map.
        ///
        /// @pre The width and height values are positive.
        /// @pre The width and height values do not exceed the maximum
        /// possible width and height for the noise map.
        ///
        /// @throw noise::ExceptionInvalidParam See the preconditions.
        /// @throw noise::ExceptionOutOfMemory Out of memory.
        ///
        /// On exit, the contents of the noise map are undefined.
        ///
        /// If the @a OUT_OF_MEMORY exception occurs, this noise map object
        /// becomes empty.
        ///
        /// If the @a INVALID_PARAM exception occurs, the noise map is
        /// unmodified.
		public void SetSize (int width, int height){
		    if (width < 0 || height < 0 || width > Utils.RASTER_MAX_WIDTH || height > Utils.RASTER_MAX_HEIGHT) {
		        // Invalid width or height.
		        throw new Exception("Invalid Param");
		    } else if (width == 0 || height == 0) {
		        // An empty noise map was specified.  Delete it and zero out the size
		        // member variables.
		        DeleteNoiseMapAndReset ();
		    } else {
		        // A new noise map size was specified.  Allocate a new noise map buffer
		        // unless the current buffer is large enough for the new noise map (we
		        // don't want costly reallocations going on.)
		        int newMemUsage = CalcMinMemUsage(width, height);
		        if (m_memUsed < newMemUsage) {
		            // The new size is too big for the current noise map buffer.  We need to
		            // reallocate.
		            DeleteNoiseMapAndReset ();
		            try {
						m_pNoiseMap = new float[newMemUsage];
		            }
		            catch (Exception) {
						throw new OutOfMemoryException();
		            }
		            m_memUsed = newMemUsage;
		        }
		        m_stride = (int)CalcStride(width);
		        m_width  = width;
		        m_height = height;
		    }
		}

        /// Sets a value at a specified position in the noise map.
        ///
        /// @param x The x coordinate of the position.
        /// @param y The y coordinate of the position.
        /// @param value The value to set at the given position.
        ///
        /// This method does nothing if the noise map object is empty or the
        /// position is outside the bounds of the noise map.
		//public void SetValue (int x, int y, float value){
		//    if (m_pNoiseMap != null) {
		//        if (x >= 0 && x < m_width && y >= 0 && y < m_height) {
		//            *(GetSlabPtr (x, y)) = value;
		//        }
		//    }
		//}

        /// Takes ownership of the buffer within the source noise map.
        ///
        /// @param source The source noise map.
        ///
        /// On exit, the source noise map object becomes empty.
        ///
        /// This method only moves the buffer pointer so this method is very
        /// quick.
		//public void TakeOwnership(NoiseMap source){
		//    // Copy the values and the noise map buffer from the source noise map to
		//    // this noise map.  Now this noise map pwnz the source buffer.
		//    delete[] m_pNoiseMap;
		//    m_memUsed   = source.m_memUsed;
		//    m_height    = source.m_height;
		//    m_pNoiseMap = source.m_pNoiseMap;
		//    m_stride    = source.m_stride;
		//    m_width     = source.m_width;

		//    // Now that the source buffer is assigned to this noise map, reset the
		//    // source noise map object.
		//    source.InitObj ();
		//}
	
        /// Returns the minimum amount of memory required to store a noise map
        /// of the specified size.
        ///
        /// @param width The width of the noise map.
        /// @param height The height of the noise map.
        ///
        /// @returns The minimum amount of memory required to store the noise
        /// map.
        ///
        /// The returned value is measured by the number of @a float values
        /// required to store the noise map, not by the number of bytes.
		private int CalcMinMemUsage(int width, int height)
		{
			return CalcStride((int)width) * (int)height;
		}

        /// Calculates the stride amount for a noise map.
        ///
        /// @param width The width of the noise map.
        ///
        /// @returns The stride amount.
        ///
        /// - The <i>stride amount</i> is the offset between the starting
        ///   points of any two adjacent slabs in a noise map.
        /// - The stride amount is measured by the number of @a float values
        ///   between these two points, not by the number of bytes.
		private int CalcStride (int width)
		{
		    return (int)(((width + Utils.RASTER_STRIDE_BOUNDARY - 1)
		        / Utils.RASTER_STRIDE_BOUNDARY) * Utils.RASTER_STRIDE_BOUNDARY);
		}

        /// Copies the contents of the buffer in the source noise map into
        /// this noise map.
        ///
        /// @param source The source noise map.
        ///
        /// @throw noise::ExceptionOutOfMemory Out of memory.
        ///
        /// This method reallocates the buffer in this noise map object if
        /// necessary.
        ///
        /// @warning This method calls the standard library function
        /// @a memcpy, which probably violates the DMCA because it can be used
        //. to make a bitwise copy of anything, like, say, a DVD.  Don't call
        /// this method if you live in the USA.
		private void CopyNoiseMap(NoiseMap source){
		    // Resize the noise map buffer, then copy the slabs from the source noise
		    // map buffer to this noise map buffer.
			//SetSize(source.GetWidth (), source.GetHeight ());
			//for (int y = 0; y < source.GetHeight (); y++) {
			//    float[] pSource = source.GetConstSlabPtr(0, y);
			//    float[] pDest = GetSlabPtr(0, y);
			//    Array.Copy(pSource, pDest, source.GetWidth());
			//}
			this.m_pNoiseMap = source.m_pNoiseMap;

		    // Copy the border value as well.
		    m_borderValue = source.m_borderValue;
		}

        /// Resets the noise map object.
        ///
        /// This method is similar to the InitObj() method, except this method
        /// deletes the buffer in this noise map.
        private void DeleteNoiseMapAndReset (){
			m_pNoiseMap = null;
			InitObj();
		}
	}
}
